/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021-12-01     xu       the first version
 */
#include "network_task.h"
#include "chinese_date_api.h"
#include "message_type.h"
#include "ntp.h"
#include "time_utils.h"

#define DBG_TAG "ui"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

#define NETWORK_MQ_MAX_MSGS 32
#define NETWORK_TASK_STACK_SIZE 8192
#define NETWORK_TASK_PRIORITY 16
#define NETWORK_TASK_TICKS 1

#define EVT_NTP_SYNC_DONE (0x1 << 0)
#define EVT_CN_DATE_GET_DONE (0x1 << 1)

static void do_ntp_sync(struct network_command* ctx)
{
    const char* ntp_host = NETUTILS_NTP_HOSTNAME;
    time_t time_value = ntp_sync_to_rtc(ntp_host);
    LOG_D("%s: time_value = %d", __func__, time_value);
}

static void do_cn_date_get(struct network_command* ctx)
{
    LOG_D("%s: enter...", __func__);
    get_current_time();

    // get first day info of current month
    time_t time_iter = current_time - (current_tm.tm_mday - 1) * SECONDS_PER_DAY;
    struct tm tm_iter = {0};
    rt_memcpy(&tm_iter, localtime(&time_iter), sizeof(tm_iter));
    int d1_wday = tm_iter.tm_wday;
    int d1_yday = tm_iter.tm_yday;

    // calculate current month days and weeks
    int month_days = get_current_month_days();
    LOG_D("month days=%d, weeks=%d", month_days);
    LOG_D("d1_wday=%d, d1_yday=%d", d1_wday, d1_yday);

    // request current month date infos
    rt_uint32_t tick_start = rt_tick_get_millisecond();
    for (int i = 0; i < month_days; i++) {
        LOG_D("Try to get cn date for %s", tm_to_date_str(&tm_iter));
        chinese_date_t cn_date = chinese_date_get(&tm_iter);
        if (cn_date) {
            LOG_D("Get cn date for %s success!", tm_to_date_str(&tm_iter));
            rt_bool_t set_ok = chinese_date_kvdb_set(&tm_iter, cn_date);
            LOG_D("Set cn date to db: %s", set_ok ? "OK" : "FAIL");
            chinese_date_delete(cn_date);
        }

        time_iter += SECONDS_PER_DAY;
        rt_memcpy(&tm_iter, localtime(&time_iter), sizeof(tm_iter));
    }
    rt_uint32_t tick_end = rt_tick_get_millisecond();
    LOG_D("Get current month chinese date time cost %d ms\n", tick_end - tick_start);

    // request current month date infos
    tick_start = rt_tick_get_millisecond();
    time_iter = current_time - (current_tm.tm_mday - 1) * SECONDS_PER_DAY;
    for (int i = 0; i < month_days; i++) {
        LOG_D("Try to get cn date for %s", tm_to_date_str(&tm_iter));
        chinese_date_t cn_date = chinese_date_kvdb_get(&tm_iter);
        if (cn_date) {
            LOG_D("Get cn date for %s success!", tm_to_date_str(&tm_iter));
            chinese_date_delete(cn_date);
        }

        time_iter += SECONDS_PER_DAY;
        rt_memcpy(&tm_iter, localtime(&time_iter), sizeof(tm_iter));
    }
    tick_end = rt_tick_get_millisecond();
    LOG_D("Query current month chinese date time cost %d ms\n", tick_end - tick_start);
}

void network_entry(void* param)
{
    rt_err_t err;
    network_task_t context = (network_task_t)param;
    if (context == RT_NULL) {
        LOG_E("%s: context null!", __func__);
        return;
    }

    RT_ASSERT(context->imq);
    LOG_I("%s task work start...", context->name);

    struct message* message = rt_malloc(sizeof(struct message));
    if (message == RT_NULL) {
        LOG_E("%s: alloc message failed!", __func__);
        return;
    }

    // notify init done, it's ready to work
    err = rt_sem_release(context->ready);
    if (err != RT_EOK) {
        LOG_E("notify ready failed!");
        rt_free(message);
        return;
    }

    while (context->running) {
        err = rt_mq_recv(context->imq, message, sizeof(*message), rt_tick_from_millisecond(30 * 1000));
        if (err == -RT_ETIMEOUT) {
            // LOG_D("%s: recv message timeout, continue %d ...", context->name, rt_tick_get_millisecond());
            continue;
        } else if (err != RT_EOK) {
            LOG_E("%s: recv message failed!", context->name);
            break;
        }
        LOG_D("%s: recved message type=%d ...", message->type);

        if (message->type != MSG_NETWORK_COMMAND) {
            continue;
        }

        struct network_command* net_cmd = &message->data.network_command;
        LOG_D("%s: process cmd %d...", context->name, net_cmd->cmd);

        context->requesting = RT_TRUE;
        switch (net_cmd->cmd) {
            case NETWORK_TIME_SYNC:
                do_ntp_sync(net_cmd);
                break;
            case NETWORK_CN_DATE_GET:
                do_cn_date_get(net_cmd);
                break;
            default:
                break;
        }
        context->requesting = RT_FALSE;
    }
    rt_free(message);

    err = rt_sem_release(context->finish);
    if (err != RT_EOK) {
        LOG_E("notiify ui task finish to semaphore failed!");
    }
    LOG_I("%s task work done...", context->name);
}

network_task_t network_task_create(rt_mq_t omq)
{
    if (omq == RT_NULL) {
        LOG_E("%s: omq null!", __func__);
        return RT_NULL;
    }

    network_task_t context = rt_malloc(sizeof(struct network_task));
    if (context == RT_NULL) {
        LOG_E("%s: alloc context failed!", __func__);
        return RT_NULL;
    }

    const char* name = "net";
    rt_mq_t imq = rt_mq_create(name, sizeof(struct message), NETWORK_MQ_MAX_MSGS, RT_IPC_FLAG_FIFO);
    if (imq == RT_NULL) {
        LOG_E("imq null!");
        rt_free(context);
        return RT_NULL;
    }

    rt_sem_t ready = rt_sem_create(name, 0, RT_IPC_FLAG_FIFO);
    if (ready == RT_NULL) {
        LOG_E("create sem %s failed!", name);
        rt_free(context);
        rt_mq_delete(imq);
        return RT_NULL;
    }

    rt_sem_t finish = rt_sem_create(name, 0, RT_IPC_FLAG_FIFO);
    if (finish == RT_NULL) {
        LOG_E("create sem %s failed!", name);
        rt_free(context);
        rt_mq_delete(imq);
        rt_sem_delete(ready);
        return RT_NULL;
    }

    rt_thread_t thread = rt_thread_create(name, network_entry, context, NETWORK_TASK_STACK_SIZE, NETWORK_TASK_PRIORITY,
                                          NETWORK_TASK_TICKS);
    if (thread == RT_NULL) {
        LOG_E("create thread %s failed!", name);
        rt_free(context);
        rt_mq_delete(imq);
        rt_sem_delete(ready);
        rt_sem_delete(finish);
        return RT_NULL;
    }

    context->name = name;
    context->running = RT_TRUE;
    context->requesting = RT_FALSE;
    context->imq = imq;
    context->omq = omq;
    context->ready = ready;
    context->finish = finish;
    context->thread = thread;
    LOG_I("ui task create done!");
    return context;
}

rt_bool_t network_task_start(network_task_t context)
{
    rt_err_t err;
    if (context == RT_NULL) {
        LOG_E("%s: context null!", __func__);
        return RT_FALSE;
    }

    err = rt_thread_startup(context->thread);
    if (err != RT_EOK) {
        LOG_E("start thread %s failed!", context->name);
        return RT_FALSE;
    }

    err = rt_sem_take(context->ready, RT_WAITING_FOREVER);
    if (err != RT_EOK) {
        LOG_E("wait %s task ready failed!", context->name);
        return RT_FALSE;
    }

    LOG_I("%s task start done!", context->name);
    return RT_TRUE;
}

rt_bool_t network_task_stop(network_task_t context)
{
    rt_err_t err;
    if (context == RT_NULL) {
        LOG_E("%s: context null!", __func__);
        return RT_FALSE;
    }

    context->running = RT_FALSE;
    rt_thread_yield();

    err = rt_sem_take(context->finish, RT_WAITING_FOREVER);
    if (err != RT_EOK) {
        LOG_E("wait %s task stop failed!", context->name);
        return RT_FALSE;
    }

    LOG_I("%s task stop done!", context->name);
    return RT_TRUE;
}

rt_bool_t network_task_destroy(network_task_t context)
{
    rt_err_t err = 0;
    if (context == RT_NULL) {
        LOG_E("%s: context null!", __func__);
        return RT_FALSE;
    }

    err |= rt_mq_delete(context->imq);
    err |= rt_sem_delete(context->ready);
    err |= rt_sem_delete(context->finish);
    err |= rt_thread_delete(context->thread);
    rt_free(context);
    if (err != RT_EOK) {
        LOG_E("%s: some error raised!", __func__);
        return RT_FALSE;
    }
    LOG_I("%s task destroy done!", context->name);
    return RT_TRUE;
}

rt_bool_t network_task_control(network_task_t context, rt_uint8_t cmd)
{
    rt_err_t err;
    if (context == RT_NULL) {
        LOG_E("%s: context null!", __func__);
        return RT_FALSE;
    }

    if (context->requesting) {
        LOG_D("network task in requesting, ignore cmd %d ...", cmd);
        return RT_TRUE;
    }

    struct message message = {0};
    message.type = MSG_NETWORK_COMMAND;
    message.data.network_command.cmd = cmd;
    LOG_D("%s: send msg type=%d, cmd=%d ...", __func__, message.type, cmd);
    err = rt_mq_send(context->imq, &message, sizeof(message));
    if (err != RT_EOK) {
        LOG_E("%s: send message to imq failed!");
        return RT_FALSE;
    }

    return RT_TRUE;
}